/*********************************
 
 连线：
 ESP8266      0.96OLED(IIC)
GPIO4(D2)  ----  SDA
GPIO5(D1)  ----  SCL

 ESP8266      0.96OLED(SPI)
GPIO14(D5)  ----  CLK
GPIO12(D6)  ----  DATA/SDA
GPIO16(D0)  ----  CS (6脚OLED可以忽略此端口)
GPIO15(D8)  ----  DC
GPIO13(D7)  ----  RESET

2022/05/08 修改：
1. 更换阿里云NTP网络时间服务器
2. 修复地区天气显示乱码问题
3. 每10分钟自动重新拉取时间、天气信息

2022/06/25 修改：
1. 增加长按 BOOT(FALSH) 键清除配置信息功能

2022/12/20 修改：
1. 更改smartConfig(一键配网)为 web 配网，主要是一键配网不稳
2. 增加WIFI指示LED ，采用开发板自带的LED, 连接上WIFI时大约每秒闪一次，WiFi断开不闪
3. 无需APP设置地区，在Web配置页面配置心知天气的密钥和地区
4. 每5分钟更新时间、天气
******************************/
#include <Arduino.h>
#include <U8g2lib.h>
#include <ESP8266WiFi.h>
#include <ArduinoJson.h>
#include <ESP8266HTTPClient.h>
#include "time.h"
#include <Ticker.h>
#include "oled.h"
#include <WiFiUdp.h>
#include <EEPROM.h>
#include <NTPClient.h> 
#include <WiFiManager.h> 

#ifdef U8X8_HAVE_HW_SPI
#include <SPI.h>
#endif
#ifdef U8X8_HAVE_HW_I2C
#include <Wire.h>
#endif

// WIFI 指示灯 大部分开发板板载的LED连接的GPIO2，如果不是，自行修改
#define LED 2   

String ssid = "";
String password = "";
String today = "";
String holiday = "";
String reqRes = "";

const char* ntpServer = "ntp1.aliyun.com"; // 阿里云NTP网络时间服务器
const long  gmtOffset_sec = 28800;
const int   daylightOffset_sec = 0;

const char *host = "api.seniverse.com"; //心知天气APIhost

// 心知天气HTTP请求所需信息
String reqUserKey = "";   // 私钥,需要再心知天气申请一个，免费版 https://www.seniverse.com/
String city = "";  // 城市
String reqUnit = "c";                    // 摄氏/华氏

// 如果不需要镜像显示 第一个参数设置为 U8G2_R0 ，开启镜像显示： U8G2_MIRROR 
// SPI 和 IIC 选择其一
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /*clock=*/SCL, /*data=*/SDA, /*reset=*/U8X8_PIN_NONE);   //IIC ESP8266 SDA==D2  SCL==D1
//U8G2_SSD1306_128X64_NONAME_F_4W_SW_SPI u8g2(U8G2_R0, /* clock=*/ 14, /* data=*/ 12, /* cs=*/ 16, /* dc=*/ 15, /* reset=*/ 13); // SPI

Ticker ticker1,ticker2,ledTicker; //声明Ticker对象
WiFiUDP Udp;
WiFiUDP ntpUDP;
uint16_t udp_port=12345;  //存储需要监听的端口号
HTTPClient httpclient;
WiFiClient client;
WiFiManager wifiManager; // 建立WiFiManager对象


unsigned int page  = 0;
unsigned int icon  = 0;

unsigned int hour = 0,minute = 0,sec = 0;
int  count = 0,count1 = 0,weatherCode = 0;
String temputer = "";

String weather1 = "";

short int mode = 0;
uint8 wifistate = 0;
bool time1 = false;
bool requestsWeatherTime = false;  // 请求日期、天气标志位a
NTPClient timeClient(ntpUDP, "ntp1.aliyun.com",60*60*8, 30*60*1000);// 阿里云NTP网络时间服务器
void connectWIFI();
void webConfig();

// LED 闪烁，快闪WIFI断链，慢闪已连接WIFI
void blinker(){
  digitalWrite(LED,!(digitalRead(LED)));
}

//a写入字符串长度，b是起始位，str为要保存的字符串
void set_String(int a,int b,String str){
  EEPROM.write(a, str.length());//EEPROM第a位，写入str字符串的长度
  //把str所有数据逐个保存在EEPROM
  for (uint8 i = 0; i < str.length(); i++){
    EEPROM.write(b + i, str[i]);
  }
  EEPROM.commit();
}

//a位是字符串长度，b是起始位
String get_String(int a, int b){ 
  String data = "";
  //从EEPROM中逐个取出每一位的值，并链接
  for (int i = 0; i < a; i++){
    data += char(EEPROM.read(b + i));
  }
  return data;
}

// 检测信息
void checkMessage(){
  if(reqUserKey == ""){
    EEPROM.begin(100);
    set_String(0,1,"none");
    u8g2.clearBuffer();
    u8g2.setCursor(8, 10);
    u8g2.print(F("没有找到心知密钥"));
    u8g2.setCursor(8, 40);
    u8g2.print(F("开始web配网"));
    u8g2.sendBuffer();
    Serial.println("没有找到心知密钥，开始web配网");
    delay(2000);
    webConfig();
  }
  if(city == ""){
     u8g2.clearBuffer();
     u8g2.setCursor(8, 10);
     u8g2.print(F("没有找到地区信息"));
     u8g2.setCursor(8, 40);
     u8g2.print(F("使用WIFI自动定位"));
     u8g2.sendBuffer();
     Serial.println("没有找到地区信息，使用连接的WIFI自动定位");
     delay(2000);
     reqRes = "/v3/weather/now.json?key=" + reqUserKey + + "&location=" + "ip" + "&language=zh-Hans&unit=" +reqUnit;
  }
  else{
      reqRes = "/v3/weather/now.json?key=" + reqUserKey + + "&location=" + city + "&language=zh-Hans&unit=" +reqUnit;
  }
  
}

//读取保存的信息
void readlastdata(){
    EEPROM.begin(100);
    ssid = get_String(EEPROM.read(0),1);
    Serial.print("ssid的值：");
    Serial.println(ssid);
    
    password = get_String(EEPROM.read(30),31);
    Serial.print("password的值：");
    Serial.println(password);

    reqUserKey = get_String(EEPROM.read(50),51);
    Serial.print("reqUserKey的值：");
    Serial.println(reqUserKey);

    city = get_String(EEPROM.read(80),81);
    Serial.print("city的值：");
    Serial.println(city);
   checkMessage();
}

// 保存数据
void savemyData(){
  Serial.println("正在保存信息");
  EEPROM.begin(100);
  set_String(0,1,ssid);
  EEPROM.begin(100);
  set_String(30,31,password);
  EEPROM.begin(100);
  set_String(50,51,reqUserKey);
  EEPROM.begin(100);
  set_String(80,81,city);
}

// 擦除保存的数据
void eraserData(){
  Serial.println("擦除信息...");
  EEPROM.begin(100);
  set_String(0,1,"none"); // 手动赋值为 none 下次启动后就会连不上WiFi 进入web配网
  Serial.println("擦除完成...");
}


//web 配网
void webConfig()
{
  Serial.println("Wait for web config");
   u8g2.clearBuffer();
   u8g2.setCursor(8, 10);
   u8g2.print(F("Wifi连接失败!"));
   u8g2.setCursor(8, 25);
   u8g2.print(F("手机连接ESP WiFi"));
   u8g2.setCursor(8, 40);
   u8g2.print(F("打开网页输入"));
   u8g2.setCursor(8, 55);
   u8g2.print(F("192.168.4.1 配置"));
   u8g2.sendBuffer();
  
  delay(2000);

  // 等待web配网
  wifiManager.startConfigPortal("ESP");

  ssid = wifiManager.getWiFiSSID();
  password = wifiManager.getWiFiPass();
  reqUserKey = wifiManager.getreqUserkey();
  city  = wifiManager.getcity();
  checkMessage();
  savemyData();

}

//连WIFI
void connectWIFI(){
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_wqy12_t_gb2312); 
  u8g2.setCursor(8, 10);
  u8g2.print(F("正在连接WIFI..."));
  u8g2.sendBuffer();

  unsigned char i = 0 ;
  WiFi.mode(WIFI_STA);
  delay(500);
  WiFi.begin(ssid.c_str(), password.c_str());    // 启动网络连接
 // Serial.print("Connecting to ");              // 串口监视器输出网络连接信息
  //Serial.print(ssid); Serial.println(" ...");  
                                  
  while (WiFi.status() != WL_CONNECTED) {      
    delay(1000); 
    i++;
    if(i>15){ // 30S 连接超时进入 
      u8g2.clearBuffer();
      //u8g2.setFont(u8g2_font_wqy12_t_gb2312); 
      u8g2.setCursor(8, 10);
      u8g2.print(F("WIFI连接失败"));
      u8g2.setCursor(8, 30);
      u8g2.print(F("检查网络或复位"));
      u8g2.sendBuffer();
      Serial.println(F("wifi 连接超时！"));
      delay(1000);
      webConfig();
    }                                                
  } 
  //Serial.println("WIFI CONNECT!");         
  u8g2.clearBuffer();
  //u8g2.setFont(u8g2_font_wqy12_t_gb2312); 
  u8g2.setCursor(8, 10);
  u8g2.println(F("WIFI已连接"));
  u8g2.sendBuffer();  
  pinMode(0,INPUT);
  
}


// 选择天气图标
void selectIcon(int i){
 switch(i){
   case 0:  // 白天晴天
   case 2:
         icon = 1;
   break;

   case 1: // 晚上晴天
   case 3:
          icon = 12;
   break; 
   case 6: // 晚上多云
   case 8:
          icon = 2;
   case 4: // 白天多云
   case 5:
   case 7:
   case 9:
          icon = 3;
   break;
   
   case 10: // 阵雨(太阳雨)
          icon = 4; 
   break;

   case 11: // 雷阵雨
   case 12:
          icon = 5;  
   break;

   case 13: // 小雨
          icon = 6;  
   break;

   case 14: // 大到暴雨
   case 15:
   case 16:
   case 17:
   case 18:
          icon = 7;  
   break;

   case 20: // 雪
   case 21:
   case 22:
   case 23:
   case 24:
   case 25:
          icon = 8;
   break;

   case 31: // 霾
          icon = 9;
   break;

   case 32: // 风
   case 33:
          icon = 10;
   break;

   default:
         icon = 99;
   break;
 }
}

// 获取网络时间
void getNtpTime()
{
  
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_wqy12_t_gb2312); 
  u8g2.setCursor(8, 10);
  u8g2.println(F("获取网络时间..."));
  u8g2.sendBuffer();   
  Serial.println(F("获取网络时间...")); 
  timeClient.begin();
  timeClient.update();
  Serial.println(timeClient.getFormattedTime());
  hour = timeClient.getHours();
  minute = timeClient.getMinutes();
  sec = timeClient.getSeconds();
}


//OLED显示日期
void oledDispalyDate(){
 u8g2.clearBuffer();         // 清除内部缓冲区
 u8g2.setFont(u8g2_font_inb24_mf);
 u8g2.setCursor(10, 25);
 u8g2.print(today.substring(5));
 u8g2.setFont(u8g2_font_wqy12_t_gb2312);
 u8g2.setCursor(45, 38);
 u8g2.print(today.substring(0,4));
 u8g2.setCursor(40, 58);
 u8g2.print(holiday);
 u8g2.sendBuffer();
}

// 更新oled显示时间
void oledDispalyTime(){

  u8g2.clearBuffer();         // 清除内部缓冲区
  u8g2.setFont(u8g2_font_inb24_mf);
  u8g2.setCursor(10, 25);
  u8g2.print(hour/10);
  u8g2.print(hour%10);
  u8g2.print(":");
  u8g2.print(minute/10);
  u8g2.print(minute%10);
  u8g2.setCursor(65, 58);
  u8g2.print(temputer);

  u8g2.setFont(u8g2_font_wqy12_t_gb2312); //12x12大小的中文字符库
  u8g2.setCursor(10, 50);
  u8g2.print(city);
  u8g2.print("-");
  u8g2.print(weather1);
  u8g2.setCursor(110, 56);
  u8g2.print("℃");

  u8g2.sendBuffer();

}


// 解析数据
void parseJson(int i ,String data){
    if(i == 0){
        DynamicJsonDocument doc(500);
        deserializeJson(doc, data); 
        //serializeJsonPretty(doc, Serial); // 串口打印JSON数据
        const char *date = doc["results"][0]["last_update"]; // 提取日期
        const char *city_name = doc["results"][0]["location"]["name"];// 提取城市名称
        const char *weather = doc["results"][0]["now"]["text"];// 天气信息
        const char *tem = doc["results"][0]["now"]["temperature"];// 温度
        int weathercode = doc["results"][0]["now"]["code"];// 天气代码
        weatherCode = weathercode;
        Serial.print("city_name: ");Serial.println(city_name);
        Serial.print("weather: ");Serial.println(weather);
        Serial.print("weathercode: ");Serial.println(weathercode);
        Serial.print("tem: ");Serial.println(tem);

        city = city_name;
        weather1 = weather;
        temputer = tem;
        today = date;
        today = today.substring(0,10);
        //Serial.println(today);
        selectIcon(weathercode);
    }
    else{
        DynamicJsonDocument doc(100);
        deserializeJson(doc, data); 
        //serializeJsonPretty(doc, Serial); // 串口打印JSON数据
        const char *date = doc["type"]["name"]; // 节假日
        holiday = date;
        if(holiday == NULL){ // 如果不是节假日
            // 先空着,还没想好怎么做
        }
      //Serial.println(holiday);
    }
}

//请求节假日信息（判断今天是否是节假日）
void getHoliday(){
  Serial.println("请求节假日信息...");
  String url = "http://timor.tech/api/holiday/info/";
  String req,res;
  req = url+today;
  if(httpclient.begin(client,req)) {
      //Serial.println("HTTPclient setUp done!");
  }
  int http_code = httpclient.GET();
      if (http_code > 0){
        Serial.printf("HTTP get code: %d\n", http_code);
        if (http_code == HTTP_CODE_OK)
        {
          res = httpclient.getString();
          httpclient.end();
          parseJson(1,res);
        }
        else
        {
          httpclient.end();
          Serial.printf("fail to get getHoliday,code:%d\n", http_code);
        }
      }
}

// 请求天气
void requestsWeather(){
 
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_wqy12_t_gb2312); 
  u8g2.setCursor(8, 10);
  u8g2.print(F("获取天气信息..."));
  u8g2.sendBuffer();   
  String req,res;
   
    // 建立http请求信息
  String httpRequest = String("GET ") + reqRes + " HTTP/1.1\r\n" + 
                              "Host: " + host + "\r\n" + 
                              "Connection: close\r\n\r\n";
  Serial.println(""); 
  Serial.print("Connecting to "); Serial.print(host);
 
  // 不知道为什么使用 httpclient 去请求总是返回 400 状态码，这里改用 wificlient 请求
  // 尝试连接服务器 
  if (client.connect(host, 80)){
    Serial.println(" Success!");
 
    // 向服务器发送http请求信息
    client.print(httpRequest);
    Serial.println("Sending request: ");
    Serial.println(httpRequest);  
 
       if (client.find("\r\n\r\n")) {  // 跳过头部信息
      Serial.println("skip Header");
    }
    res = client.readString(); // 读取 响应主体
    //Serial.print("res: ");
    //Serial.println(res);

   
    parseJson(0,res); //  解析响应信息
    
  } else {
    Serial.println(" connection failed!");
  }   
  
  client.stop(); //断开客户端与服务器连接工作
}




// 定时器1中断函数
void tickerIsr(){
  if(WiFi.status() == WL_CONNECTED){
     digitalWrite(LED,!(digitalRead(LED)));
  }
  else{
    digitalWrite(LED,1);
  }
  timeClient.update(); // 更新时间
  hour = timeClient.getHours();
  minute = timeClient.getMinutes();
  sec = timeClient.getSeconds();
  count++;
  count1++;
 if( count1>0 && count1<=5){
   mode = 1;
 } 
else if(count1>5 && count1<=10){
    mode = 2;
}
else if(count1>10 && count1<=15){
  mode = 3;
}
else{
  count1 = 0;
}
  if(count>300){ // 每5分钟复位一次，重新获取时间、天气
    Serial.println("重新获取时间、天气....");
    count = 0;
    requestsWeatherTime = true;
  }
}

void ticker2Isr(){
  time1 = !time1;
}

// 显示欢迎页面
void welcome(){
  u8g2.clearBuffer();
  u8g2.setFont(u8g2_font_wqy12_t_gb2312); 
  u8g2.setCursor(8, 10);
  u8g2.print(F("我是一条咸鱼....."));
  u8g2.setCursor(15, 25);
  u8g2.print(F("翻过身也是...."));
  u8g2.sendBuffer(); 
  delay(3000);
}

void setup(void) {
  u8g2.setBusClock(4000000);
  u8g2.begin();
  u8g2.enableUTF8Print();//开启中文显示
  Serial.begin(115200);
  pinMode(LED,OUTPUT);
  digitalWrite(LED,1);
  welcome();
  readlastdata();
  //configTime(gmtOffset_sec, daylightOffset_sec, ntpServer); 
  connectWIFI();
  requestsWeather();
  getHoliday();
  getNtpTime();
  ticker1.attach(1,tickerIsr); // 每1秒执行中断函数
  ticker2.attach_ms(20,ticker2Isr);// 每20ms秒执行中断函数

}

void loop(void) {

  if(requestsWeatherTime){ // 請求日期天气标志位
    requestsWeatherTime = false;
    getNtpTime();
    getHoliday();
    requestsWeather();
  } 

  if(digitalRead(0) == LOW){
    delay(3000);
    if(digitalRead(0) == LOW){
      u8g2.clearBuffer();
      u8g2.setCursor(8, 10);
      u8g2.print(F("清除数据...."));
      eraserData();
      delay(1000);
      u8g2.setCursor(8, 30);
      u8g2.print(F("即将重启"));
      u8g2.sendBuffer();
      delay(1000);
      ESP.restart();
    }
  }
  if(mode ==3){
     if(time1){
          if( !(hour >= 6 && hour <= 18) && icon == 3 ) {icon = 2;} // 如果是晚上并且是阴天或者多云，修改图标
          if( !(hour >= 6 && hour <= 18) && icon == 4 ) {icon = 5;} // 如果是晚上并且是阵雨，修改图标
          if(page >= 26) page = 0;
            switch (icon)
            {
                case 1:
                    drawsunnyBmp(page);
                    break;
                case 2:
                    drawnightBmp(page);
                    break;
                case 3:
                    drawcloudyBmp(page);
                    break;  
                case 4:
                    drawrain_cloudyBmp(page);
                    break;
                case 5:
                    drawthundershowerBmp(page);
                    break;
                case 6:
                    drawrainyBmp(page);
                    break;
                case 7:
                    drawheavyrainBmp(page);
                    break;
                case 8:
                    drawsnowyBmp(page);
                    break;
                case 9:
                    drawhazeBmp(page);
                    break;
                case 10:
                    drawwindyBmp(page);
                    break;
                case 12:
                    drawnightMoonbmp(page);
                    break;
                default:
                    drawunknowbmp(page);
                  break;
          }
      page+=1;
    }  
  }
 else if(mode == 2){
   if(time1){
     //lasttime = millis();
     oledDispalyTime();
   }
 }
 else if(mode == 1){
    if(time1){
     //lasttime = millis();
     oledDispalyDate();
   }
 }
}
